﻿#define CONTRACTS_FULL //for R#

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading;

namespace GPLib
{

    /// <summary>
    /// Creates a threaded garbage pump that rapidly floods the Windows space with bogus keystrokes.
    /// </summary>
    internal sealed class GarbagePumpEngine : IDisposable
    {

        //Error callbacks for errors that occur on the GP thread.
        internal delegate void DOnError( string msg, bool isFatal );
        private readonly DOnError _onerrorcallback;

        // Milliseconds for next modifier check.  When a modifier is held down by the user, the GP halts until the user releases.
        private const int MOD_WAIT_INTERVAL = 1;

        private const int THREAD_WAIT_INTERVAL = 100;

        private bool _isRunning;
        private bool _stoppedRunning;
        private bool _isDisposed;

        //The GP only accepts unmodified and shift-only characters at this time. AltGr is there for future implementation.
        private readonly char[] _normalchars;
        private readonly char[] _shiftchars;

        private readonly List< char > _randomBuffer; 

        private readonly object _lockobject = new object();

        /*
         * Key press random delay range.
         */
        private int _minSleep = 1;
        private int _maxSleep = 30;

        /// <summary>
        /// Creates an instance of the Garbage Pump.
        /// </summary>
        /// <param name="onerrorcallback">An error occur on a different thread, indicating the thread must stop.  The notification is sent to this method.</param>
        /// <param name="unmodifiedchars">Characters that require no modifiers to create</param>
        internal GarbagePumpEngine( DOnError onerrorcallback, char[] unmodifiedchars )
            : this( onerrorcallback, unmodifiedchars, null )
        {
        }

        /// <summary>
        /// Creates an instance of the experimental Garbage Pump that allows shift keys.  This is not currently stable.
        /// </summary>
        /// <param name="onerrorcallback">An error occur on a different thread, indicating the thread must stop.  The notification is sent to this method.</param>
        /// <param name="unmodifiedchars">Characters that require no modifiers to create</param>
        /// <param name="shiftchars">Characters that require the shift modifier to create</param>
        internal GarbagePumpEngine( DOnError onerrorcallback, char[] unmodifiedchars, char[] shiftchars )
        {
            //These CANNOT be null, under any circumstances.
            Contract.Assert( unmodifiedchars != null && onerrorcallback != null );

            _onerrorcallback = onerrorcallback;
            _normalchars = unmodifiedchars;
            _shiftchars = shiftchars;             //optional: so it can be null
            _randomBuffer = new List< char >();

        }

        #region GARBAGE PUMP CONTROLS

        /// <summary>
        /// Returns whether the Garbage Pump is running or not.
        /// </summary>
        internal bool IsRunning
        {
            get { return _isRunning; }
        }

        /// <summary>
        /// Gets or sets the smallest interval possible (in milliseconds) between key presses. The delay is a random number 
        /// between and including MinSleep and MaxSleep.
        /// </summary>
        internal int MinSleep
        {
            get { return _minSleep; }
            set
            {
                Contract.Assert( value >= 0 && value < _maxSleep );
                _minSleep = value;
            }
        }

        /// <summary>
        /// Gets or sets the largest interval possible (in milliseconds) between key presses. The delay is a random number 
        /// between and including MinSleep and MaxSleep.
        /// </summary>
        internal int MaxSleep
        {
            get { return _maxSleep; }
            set
            {
                Contract.Assert( value > 0 && value > _minSleep );
                _maxSleep = value;
            }
        }

        /// <summary>
        /// Returns the Random keys generated by the Garbage Pump.  The pump CANNOT be running.
        /// </summary>
        internal List< char > RandomKeys
        {
            get
            {
                Contract.Assert( !_isRunning );
                return _randomBuffer;
            }
        }

        /// <summary>
        /// Attempts to start the Garbage Pump.
        /// </summary>
        /// <param name="timeoutInMilliseconds">Timeout if unable to start</param>
        /// <returns>True if started</returns>
        internal bool AsyncStart( int timeoutInMilliseconds )
        {

            if ( _isRunning )
                return true;    //already running

            _randomBuffer.Clear();
            ThreadPool.QueueUserWorkItem( ThreadedPump );

            //_stoppedRunning == false then the thread has started
            return PerformWait( ref _stoppedRunning, false, timeoutInMilliseconds );

        }

        /// <summary>
        /// Attempts to stop the Garbage Pump.
        /// </summary>
        /// <param name="timeoutInMilliseconds">Timeout if unable to start</param>
        /// <returns>True if stopped</returns>
        internal bool AsyncStop( int timeoutInMilliseconds )
        {

            if ( !_isRunning )
                return true;

            _isRunning = false;

            //_stoppedRunning == true then the thread has stopped
            return PerformWait( ref _stoppedRunning, true, timeoutInMilliseconds );

        }
        
        /// <summary>
        /// Handles waiting on a single condition from a single thread.
        /// </summary>
        /// <param name="condition">Condition to monitor</param>
        /// <param name="matchCondition">Check matchCondition equals condition</param>
        /// <param name="timeoutInMilliseconds">Timeout after this many milliseconds</param>
        /// <returns>True if the condition is met within the timeframe given, otherwise False</returns>
        private static bool PerformWait( ref bool condition, bool matchCondition, int timeoutInMilliseconds )
        {

            int timeoutAdd = 0;

            while ( condition != matchCondition )
            {

                Thread.Sleep( THREAD_WAIT_INTERVAL );

                timeoutAdd += THREAD_WAIT_INTERVAL;

                if ( timeoutAdd > timeoutInMilliseconds )
                    return false;

            }

            return true;

        }

        #endregion

        #region GARBAGE PUMP ENGINE

        /// <summary>
        /// Main loop for the garbage pump.
        /// </summary>
        /// <param name="context">null</param>
        private void ThreadedPump( object context )
        {

            _isRunning = true;
            _stoppedRunning = false;

            while ( _isRunning )
            {

                Random r = new Random();

                while ( _isRunning )
                {

                    /*
                     * To help prevent time analysis attacks, this thread will sleep for a very small random amount of time between 
                     * simulated keystrokes.  The range is set via the MinSleep and MaxSleep methods.
                     */
                    int nextSleepDuration = r.Next( _minSleep, _maxSleep );

                    bool isShifted = ( _shiftchars != null && ( Environment.TickCount & 1 ) == 1 ); //if odd then use shift, otherwise use normal

                    char nextchar = ( isShifted
                      ? _shiftchars[ r.Next( 0, _shiftchars.Length - 1 ) ]
                      : _normalchars[ r.Next( 0, _normalchars.Length - 1 ) ] );

                    //ran into a send error, stop urvrything!
                    if ( !NativeSendKey( nextchar, isShifted ) )
                    {
                        _isRunning = false;
                        _onerrorcallback( @"Unable to send correct amount of keys to Windows.", true );
                        break;
                    }

                    //Add the char once it's been sent
                    _randomBuffer.Add( nextchar );

                    //Delay the next keystroke
                    Thread.Sleep( nextSleepDuration );

                }

            }

            _stoppedRunning = true;

        }

        internal unsafe void ClearKeyboardState()
        {
            lock ( _lockobject )
            {
                byte* spkbstate = stackalloc byte[256];
                WinNative.GetKeyboardState( spkbstate );
                WinNative.memset( spkbstate, 0, sizeof ( byte ) * 256 ); //unset all virtual keys
                WinNative.SetKeyboardState( spkbstate );
            }
        }

        /// <summary>
        /// A native send key is used over the built-in .NET SendKeys because it has less overhead and doesn't block user input.
        /// </summary>
        /// <param name="c">The character to send</param>
        /// <param name="requiresShift">True if the character requires a shift modifier</param>
        /// <returns>True if all the inputs were successfully sent</returns>
        private bool NativeSendKey( char c, bool requiresShift )
        {

            bool successful;

            unsafe
            {

                int inputs = 2;
                int offset = 0;

                if ( requiresShift ) //expand for shift encapsulation
                {
                    inputs = 4;
                    offset = 1;
                }

                WinNative.Input* spInputs = stackalloc WinNative.Input[ inputs ];

                /*
                     If a shift is required then the key message stack must look like this:
                        Shift        Down
                            Char     Down
                            Char     Up
                        Shift        Up
                 */
                if ( requiresShift )
                {
                    spInputs[ 0 ].type = spInputs[ 3 ].type = ( int )WinNative.InputType.Keyboard;
                    spInputs[ 0 ].iu.ki.wVk = spInputs[ 3 ].iu.ki.wVk = 0xA0; //left shift, right shift = 0xA1
                    spInputs[ 0 ].iu.ki.wScan = spInputs[ 3 ].iu.ki.wScan = 0;
                    spInputs[ 0 ].iu.ki.time = spInputs[ 3 ].iu.ki.time = 0;
                    spInputs[ 0 ].iu.ki.dwExtraInfo = spInputs[ 3 ].iu.ki.dwExtraInfo = UIntPtr.Zero;
                    spInputs[ 0 ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfNone;
                    spInputs[ 3 ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfKeyup;
                }

                if ( c > 254 ) //Must use Unicode protocol if out of wVk range (1 - 254 )
                {
                    spInputs[ offset ].type = spInputs[ offset + 1 ].type = ( int )WinNative.InputType.Keyboard;
                    spInputs[ offset ].iu.ki.wVk = spInputs[ offset + 1 ].iu.ki.wVk = 0;                                //set to zero
                    spInputs[ offset ].iu.ki.wScan = spInputs[ offset + 1 ].iu.ki.wScan = Convert.ToUInt16( c );        //insert unicode char
                    spInputs[ offset ].iu.ki.time = spInputs[ offset + 1 ].iu.ki.time = 0;
                    spInputs[ offset ].iu.ki.dwExtraInfo = spInputs[ offset + 1 ].iu.ki.dwExtraInfo = UIntPtr.Zero;
                    spInputs[ offset ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfUnicode;
                    spInputs[ offset + 1 ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfKeyup |
                                                            ( uint )WinNative.KbFlags.KeyeventfUnicode;
                }
                else
                {
                    spInputs[ offset ].type = spInputs[ offset + 1 ].type = ( int )WinNative.InputType.Keyboard;
                    spInputs[ offset ].iu.ki.wVk = spInputs[ offset + 1 ].iu.ki.wVk = WinNative.VkKeyScan( c );
                    spInputs[ offset ].iu.ki.wScan = spInputs[ offset + 1 ].iu.ki.wScan = 0;
                    spInputs[ offset ].iu.ki.time = spInputs[ offset + 1 ].iu.ki.time = 0;
                    spInputs[ offset ].iu.ki.dwExtraInfo = spInputs[ offset + 1 ].iu.ki.dwExtraInfo = UIntPtr.Zero;
                    spInputs[ offset ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfNone;
                    spInputs[ offset + 1 ].iu.ki.dwFlags = ( uint )WinNative.KbFlags.KeyeventfKeyup;
                }

                /*
                 * It's impossible or at least not worth the trouble to perform synchronizations between virtual input and 
                 * user input.  As a result, any time one of the modifier keys is down, the garbage pump ceases until 
                 * the user releases them.
                 */
                while ( ( ( ( ushort )WinNative.GetAsyncKeyState( ( int )WinNative.VkMods.VkShift ) |
                            ( ushort )WinNative.GetAsyncKeyState( ( int )WinNative.VkMods.VkControl ) |
                            ( ushort )WinNative.GetAsyncKeyState( ( int )WinNative.VkMods.VkMenu ) ) & 0x8000 ) != 0 )
                    Thread.Sleep( MOD_WAIT_INTERVAL ); //periodically check it.

                //ClearKeyboardState();
                

                //Caps lock can never be enabled
                if ( ( WinNative.GetKeyState( ( int )WinNative.VkMods.VkCapital ) & 0x1 ) != 0 )
                {
                    _isRunning = false;
                    _onerrorcallback( "Caps lock cannot be pressed while garbage pump is running.", false );
                    return true; //return true so as not to invoke 2 error methods.
                }

                lock ( _lockobject )
                    successful = ( WinNative.SendInput( ( uint )inputs, spInputs, sizeof( WinNative.Input ) ) == inputs );

            }

            return successful;

        }

        #endregion

        public void Dispose()
        {

            if ( !_isDisposed )
                return;

            AsyncStop( 3000 ); //stop the thread
            _isDisposed = true;

        }

    }

}
